﻿/* System.Compute: a Computation Framework for .NET/Mono
 * Copyright (C) 2011 Ralf "raboni" Boeckhorst
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */

using System.Runtime.InteropServices;
using System.Text;

namespace System.Compute.OpenCL
{
    public sealed class Device : ICloneable, IDisposable
    {
        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceIDs(IntPtr platform, DeviceType device_type, SizeT num_entries, IntPtr[] devices, ref SizeT num_devices);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceIDs(IntPtr platform, DeviceType device_type, SizeT num_entries, IntPtr[] devices, SizeT num_devices);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, IntPtr param_value, ref SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, IntPtr param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref uint param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceFPConfig param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceExecutionCapabilities param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, byte[] param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref ulong param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceMemCacheType param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref SizeT param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceLocalMemType param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref IntPtr param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref CommandQueueProperties param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceType param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clGetDeviceInfo(IntPtr device, DeviceInfo param_name, SizeT param_value_size, ref DeviceAffinityDomain param_value, SizeT param_value_size_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clCreateSubDevices(IntPtr in_device, DevicePartitionProperty[] properties, uint num_devices, IntPtr[] out_devices, ref uint num_devices_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clCreateSubDevices(IntPtr in_device, DevicePartitionProperty[] properties, uint num_devices, IntPtr[] out_devices, uint num_devices_ret);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clRetainDevice(IntPtr device);

        [DllImport("opencl.dll", ExactSpelling = true)]
        static extern ErrorCode clReleaseDevice(IntPtr device);

        public uint AddressBits
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.AddressBits, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public bool Available
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Available, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public bool CompilerAvailable
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.CompilerAvailable, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public DeviceFPConfig DoubleFPConfig
        {
            get
            {
                DeviceFPConfig ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.DoubleFPConfig, sizeof(DeviceFPConfig), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public bool EndianLittle
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.EndianLittle, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public bool ErrorCorrectionSupport
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ErrorCorrectionSupport, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public DeviceExecutionCapabilities ExecutionCapabilities
        {
            get
            {
                DeviceExecutionCapabilities ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ExecutionCapabilities, sizeof(DeviceExecutionCapabilities), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public string Extensions
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Extensions, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.Extensions, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public ulong GlobalMemCacheSize
        {
            get
            {
                ulong ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.GlobalMemCacheSize, sizeof(ulong), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceMemCacheType GlobalMemCacheType
        {
            get
            {
                DeviceMemCacheType ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.GlobalMemCacheType, sizeof(DeviceMemCacheType), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint GlobalMemCachelineSize
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.GlobalMemCachelineSize, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public ulong GlobalMemSize
        {
            get
            {
                ulong ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.GlobalMemSize, sizeof(ulong), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceFPConfig HalfFPConfig
        {
            get
            {
                DeviceFPConfig ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.HalfFPConfig, sizeof(DeviceFPConfig), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public bool HostUnifiedMemory
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.HostUnifiedMemory, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public bool ImageSupport
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ImageSupport, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public SizeT Image2DMaxHeight
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Image2DMaxHeight, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT Image2DMaxWidth
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Image2DMaxWidth, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT Image3DMaxDepth
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Image3DMaxDepth, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT Image3DMaxHeight
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Image3DMaxHeight, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT Image3DMaxWidth
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Image3DMaxWidth, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT ImageMaxBufferSize
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ImageMaxBufferSize, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT ImageMaxArraySize
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ImageMaxArraySize, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public bool LinkerAvailable
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.LinkerAvailable, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public ulong LocalMemSize
        {
            get
            {
                ulong ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.LocalMemSize, sizeof(ulong), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceLocalMemType LocalMemType
        {
            get
            {
                DeviceLocalMemType ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.LocalMemType, sizeof(DeviceLocalMemType), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxClockFrequency
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxClockFrequency, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxComputeUnits
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxComputeUnits, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxConstantArgs
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxConstantArgs, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public ulong MaxConstantBufferSize
        {
            get
            {
                ulong ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxConstantBufferSize, sizeof(ulong), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public ulong MaxMemAllocSize
        {
            get
            {
                ulong ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxMemAllocSize, sizeof(ulong), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT MaxParameterSize
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxParameterSize, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxReadImageArgs
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxReadImageArgs, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxSamplers
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxSamplers, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxSubDevices
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxSubDevices, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT MaxWorkGroupSize
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxWorkGroupSize, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxWorkItemDimensions
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxWorkItemDimensions, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT[] MaxWorkItemSizes
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxWorkItemSizes, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                SizeT[] ret = new SizeT[(long)param_value_size / (long)param_value_size.Size];
                GCHandle h = GCHandle.Alloc(ret, GCHandleType.Pinned);
                res = clGetDeviceInfo(Handle, DeviceInfo.MaxWorkItemSizes, param_value_size, h.AddrOfPinnedObject(), 0);
                h.Free();
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MaxWriteImageArgs
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MaxWriteImageArgs, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint MemBaseAddrAlign
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MemBaseAddrAlign, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        [Obsolete("Deprecated as of OpenCL 1.2")]
        public uint MinDataTypeAlignSize
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.MinDataTypeAlignSize, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public string Name
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Name, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.Name, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public uint NativeVectorWidthChar
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthChar, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthShort
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthShort, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthInt
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthInt, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthLong
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthLong, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthFloat
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthFloat, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthDouble
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthDouble, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint NativeVectorWidthHalf
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.NativeVectorWidthHalf, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public string OpenCLCVersion
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.OpenCLCVersion, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.OpenCLCVersion, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public Device ParentDevice
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Platform, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return new Device(ret);
            }
        }

        public uint PartitionMaxSubDevices
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PartitionMaxSubDevices, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DevicePartitionProperty[] PartitionProperties
        {
            get
            {
                SizeT size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PartitionProperties, 0, IntPtr.Zero, ref size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                DevicePartitionProperty[] ret = new DevicePartitionProperty[size / sizeof(DevicePartitionProperty)];
                GCHandle handle = GCHandle.Alloc(ret, GCHandleType.Pinned);
                res = clGetDeviceInfo(Handle, DeviceInfo.PartitionProperties, size, handle.AddrOfPinnedObject(), 0);
                handle.Free();
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceAffinityDomain PartitionAffinityDomain
        {
            get
            {
                DeviceAffinityDomain ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PartitionAffinityDomain, sizeof(DeviceAffinityDomain), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DevicePartitionProperty[] PartitionType
        {
            get
            {
                SizeT size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PartitionType, 0, IntPtr.Zero, ref size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                DevicePartitionProperty[] ret = new DevicePartitionProperty[size / sizeof(DevicePartitionProperty)];
                GCHandle handle = GCHandle.Alloc(ret, GCHandleType.Pinned);
                res = clGetDeviceInfo(Handle, DeviceInfo.PartitionType, size, handle.AddrOfPinnedObject(), 0);
                handle.Free();
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public Platform Platform
        {
            get
            {
                IntPtr ret = IntPtr.Zero;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Platform, IntPtr.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return new Platform(ret);
            }
        }

        public uint PreferredVectorWidthChar
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthChar, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthShort
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthShort, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthInt
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthInt, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthLong
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthLong, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthFloat
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthFloat, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthDouble
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthDouble, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public uint PreferredVectorWidthHalf
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredVectorWidthHalf, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public SizeT PrintFBufferSize
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PrintFBufferSize, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public bool PreferredInteropUserSync
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.PreferredInteropUserSync, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret > 0;
            }
        }

        public string Profile
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Profile, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.Profile, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public SizeT ProfilingTimerResolution
        {
            get
            {
                SizeT ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.ProfilingTimerResolution, ret.Size, ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public CommandQueueProperties QueueProperties
        {
            get
            {
                CommandQueueProperties ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.QueueProperties, sizeof(CommandQueueProperties), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceFPConfig SingleFPConfig
        {
            get
            {
                DeviceFPConfig ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.SingleFPConfig, sizeof(DeviceFPConfig), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public DeviceType Type
        {
            get
            {
                DeviceType ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Type, sizeof(DeviceType), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public string Vendor
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Vendor, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.Vendor, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public uint VendorId
        {
            get
            {
                uint ret = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.VendorId, sizeof(uint), ref ret, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return ret;
            }
        }

        public string Version
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.Version, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.Version, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public string DriverVersion
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.DriverVersion, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.DriverVersion, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        public string BuiltInKernels
        {
            get
            {
                SizeT param_value_size = 0;
                ErrorCode res = clGetDeviceInfo(Handle, DeviceInfo.BuiltInKernel, 0, IntPtr.Zero, ref param_value_size);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                byte[] data = new byte[(long)param_value_size];
                res = clGetDeviceInfo(Handle, DeviceInfo.BuiltInKernel, param_value_size, data, 0);
                if (res != ErrorCode.Success)
                    throw new OpenCLException(res);
                return Encoding.Default.GetString(data);
            }
        }

        internal IntPtr Handle;

        internal Device(IntPtr handle)
        {
            Handle = handle;
        }

        public static Device[] GetDevices(Platform platform, DeviceType device_type)
        {
            SizeT num_devices = 0;
            ErrorCode res = clGetDeviceIDs(platform.Handle, device_type, 0, null, ref num_devices);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            IntPtr[] devices = new IntPtr[(long)num_devices];
            res = clGetDeviceIDs(platform.Handle, device_type, num_devices, devices, 0);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            
            Device[] arr = new Device[(long)num_devices];
            for (long i = 0; i < num_devices; i++)
            {
                arr[i] = new Device(devices[i]);
            }

            return arr;
        }

        public Device[] CreateSubDevices(DevicePartitionProperty[] properties)
        {
            uint num_devices = 0;
            ErrorCode res = clCreateSubDevices(Handle, properties, 0, null, ref num_devices);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            IntPtr[] devices = new IntPtr[num_devices];
            res = clCreateSubDevices(Handle, properties, num_devices, devices, 0);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);

            Device[] arr = new Device[num_devices];
            for (long i = 0; i < num_devices; i++)
            {
                arr[i] = new Device(devices[i]);
            }

            return arr;
        }

        public object Clone()
        {
            ErrorCode res = clRetainDevice(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            return new Device(Handle);
        }

        public void Dispose()
        {
            ErrorCode res = clReleaseDevice(Handle);
            if (res != ErrorCode.Success)
                throw new OpenCLException(res);
            Handle = IntPtr.Zero;
        }
    }
}
